package goading_cache

import (
	"sync"
	"time"
)

// 本地缓存对象
type LoadingCache[T any] struct {
	CacheKey      string
	Func          func() (T, error)
	CacheMillis   int64 //缓存最大时长
	RefreshMillis int64 //生成缓存后超过时长主动刷新一次，避免堆积到ExpireAt时刷新，如果qps不高可设置为0
	HasVal        bool
	CacheVal      T
	RefreshAt     time.Time
	ExpireAt      time.Time
	once          *sync.Once
	nextOnceAt    time.Time
	nextOnceMutex *sync.Once
	refreshOnce   *sync.Once
}

func NewLoadingCache[T any](cacheKey string, cacheMillis, refreshMillis int64, f func() (T, error)) *LoadingCache[T] {
	return &LoadingCache[T]{
		CacheKey:      cacheKey,
		Func:          f,
		CacheMillis:   cacheMillis,
		RefreshMillis: refreshMillis,
		once:          new(sync.Once),
		nextOnceMutex: new(sync.Once),
		refreshOnce:   new(sync.Once),
	}
}

// 获取缓存对象
func (c *LoadingCache[T]) Get() (*LoadingCache[T], error) {
	if c.HasVal && c.ExpireAt.After(time.Now()) {
		//如果设置了主动刷新，则过了刷新时间就主动刷新一次
		if !c.RefreshAt.IsZero() && c.RefreshAt.Before(time.Now()) {
			c.refreshOnce.Do(func() {
				go c.refresh()
			})
		}
		return c, nil
	}

	return c.refresh()
}

// 简单本地缓存更新
func (c *LoadingCache[T]) refresh() (*LoadingCache[T], error) {
	//第一次、或者达到达到最大缓存时间、主动刷新时间，都对once进行重置
	if c.nextOnceAt.Before(time.Now()) {
		//避免并发问题 超高并发时可能会有极少量请求重复执行，但内部逻辑无甚耗时，不影响效果
		c.nextOnceMutex.Do(func() {
			defer func() {
				logger.Infof("nextOnceMutex %+v", c.CacheKey)
				c.nextOnceMutex = new(sync.Once)
			}()
			now := time.Now()
			expireAt := now.Add(time.Millisecond * time.Duration(c.CacheMillis))
			refreshAt := now.Add(time.Millisecond * time.Duration(c.RefreshMillis))
			if c.nextOnceAt.Before(now) {
				//减少refresh触发次数
				if c.RefreshMillis > 0 {
					c.RefreshAt = refreshAt
				}
				if c.RefreshMillis > 0 && refreshAt.Before(expireAt) {
					c.nextOnceAt = refreshAt
				} else {
					c.nextOnceAt = expireAt
				}
				c.once = new(sync.Once)
			}
		})
	}
	//多次请求合并为同一个执行获取数据
	c.once.Do(func() {
		defer func() {
			logger.Infof("once %+v", c.CacheKey)
			if err := recover(); err != nil {
				logger.Errorf("loading cache panic cache: %+v err: %v", c.CacheKey, err)
				//panic 1秒后重置
				c.nextOnceAt = time.Now().Add(time.Second * 1)
			}
		}()
		val, err := c.Func()
		if nil != err {
			logger.Errorf("获取缓存对象 %v 报错=%+v", c.CacheKey, err)
			return
		}
		c.HasVal = true
		c.CacheVal = val

		//下次刷新时间
		now := time.Now()
		expireAt := now.Add(time.Millisecond * time.Duration(c.CacheMillis))
		refreshAt := now.Add(time.Millisecond * time.Duration(c.RefreshMillis))
		//重置过期时间和refresh时间
		c.ExpireAt = expireAt
		if c.RefreshMillis > 0 {
			c.RefreshAt = refreshAt
			c.refreshOnce = new(sync.Once)
		}
		//下次可以执行时间
		if c.RefreshMillis > 0 && refreshAt.Before(expireAt) {
			c.nextOnceAt = refreshAt
		} else {
			c.nextOnceAt = expireAt
		}
	})
	return c, nil
}
